/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.services.project;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.ServiceException;
import cn.easyplatform.cfg.EngineConfiguration;
import cn.easyplatform.dao.BizDao;
import cn.easyplatform.dao.DaoException;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.stream.StringInputStream;
import cn.easyplatform.messages.vos.admin.CustomVo;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.type.FieldType;
import org.apache.shiro.config.Ini;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ProjectConfig {

    private static final Logger log = LoggerFactory.getLogger(ProjectConfig.class);

    private EngineConfiguration engineConfiguration;

    private IProjectService ps;

    private Map<String, String> context;

    private Ini config;

    //业务表sys_config_info
    private Map<String, Map<String, String>> sysConfig;

    private Pattern pattern;

    ProjectConfig(EngineConfiguration engineConfiguration, IProjectService ps,
                  Map<String, String> context) {
        this.engineConfiguration = engineConfiguration;
        this.ps = ps;
        if (context.containsKey("config")) {
            config = new Ini();
            config.load(new StringInputStream(context.get("config").trim()));
        }
        this.context = context;
        if (!this.context.containsKey("dao.authenticationQuery")) {
            if (engineConfiguration.getPlatformAttribute("dao.authenticationQuery") != null)
                this.context.put("dao.authenticationQuery", engineConfiguration.getPlatformAttribute("dao.authenticationQuery"));
            else throw new ServiceException("dao.authenticationQuery not found.");
        }

        if (!this.context.containsKey("dao.userOrgQuery")) {
            if (engineConfiguration.getPlatformAttribute("dao.userOrgQuery") != null)
                this.context.put("dao.userOrgQuery", engineConfiguration.getPlatformAttribute("dao.userOrgQuery"));
            else
                throw new ServiceException("dao.userOrgQuery not found.");
        }

        if (!this.context.containsKey("dao.userAgentQuery") && engineConfiguration.getPlatformAttribute("dao.userAgentQuery") != null)
            this.context.put("dao.userAgentQuery", engineConfiguration.getPlatformAttribute("dao.userAgentQuery"));

        if (!this.context.containsKey("dao.userAgentRoleQuery") && engineConfiguration.getPlatformAttribute("dao.userAgentRoleQuery") != null)
            this.context.put("dao.userAgentRoleQuery", engineConfiguration.getPlatformAttribute("dao.userAgentRoleQuery"));

        if (!this.context.containsKey("dao.orgSubQuery") && engineConfiguration.getPlatformAttribute("dao.orgSubQuery") != null)
            this.context.put("dao.orgSubQuery", engineConfiguration.getPlatformAttribute("dao.orgSubQuery"));

        if (!this.context.containsKey("dao.accQuery") && engineConfiguration.getPlatformAttribute("dao.accQuery") != null)
            this.context.put("dao.accQuery", engineConfiguration.getPlatformAttribute("dao.accQuery"));

        if (!this.context.containsKey("user.maxLoginFailedTimes")) {
            if (engineConfiguration.getPlatformAttribute("user.maxLoginFailedTimes") != null)
                this.context.put("user.maxLoginFailedTimes", engineConfiguration.getPlatformAttribute("user.maxLoginFailedTimes"));
            else
                this.context.put("user.maxLoginFailedTimes", UserDo.DEFAULT_MAX_LOGIN_FAILED_TIMES + "");
        }

        if (!this.context.containsKey("user.caculateDaysRemaining")) {
            if (engineConfiguration.getPlatformAttribute("user.caculateDaysRemaining") != null)
                this.context.put("user.caculateDaysRemaining", engineConfiguration.getPlatformAttribute("user.caculateDaysRemaining"));
            else
                this.context.put("user.caculateDaysRemaining", "0");
        }

        if (!this.context.containsKey("user.timeToLive")) {
            if (engineConfiguration.getPlatformAttribute("user.timeToLive") != null)
                this.context.put("user.timeToLive", engineConfiguration.getPlatformAttribute("user.timeToLive"));
            else
                this.context.put("user.timeToLive", UserDo.DEFAULT_TIMEOUT + "");
        }

        if (!this.context.containsKey("user.timeout"))
            this.context.put("user.timeout", engineConfiguration.getSessionTimeout() + "");

        if (!this.context.containsKey("jwt.timeout"))
            this.context.put("jwt.timeout", this.context.get("user.timeout"));

        if (!this.context.containsKey("user.strictMode")) {
            if (engineConfiguration.getPlatformAttribute("user.strictMode") != null) {
                String val = engineConfiguration.getPlatformAttribute("user.strictMode");
                if (val.length() > 1)
                    this.context.put("user.strictMode", "0");
                else
                    this.context.put("user.strictMode", val);
            } else
                this.context.put("user.strictMode", "0");
        } else {
            String val = context.get("user.strictMode");
            if (val.length() > 1)
                this.context.put("user.strictMode", val.equals("false") ? "0" : "1");
            else
                this.context.put("user.strictMode", val);
        }

        if (!this.context.containsKey("app.logLevel")) {
            if (engineConfiguration.getPlatformAttribute("app.logLevel") != null)
                this.context.put("app.logLevel", engineConfiguration.getPlatformAttribute("app.logLevel"));
            else
                this.context.put("app.logLevel", "3");
        }
        String expr = getString("user.password.check.expr");
        if (!Strings.isBlank(expr))
            pattern = Pattern.compile(expr);
        context.entrySet().removeIf(entry -> entry.getKey().startsWith("app.task"));
        sysConfig = new ConcurrentHashMap<>();
        load(null);
        /*if (!this.context.containsKey("businessDateHandlerFactory"))
            this.context.put("businessDateHandlerFactory", engineConfiguration.getPlatformAttribute("businessDateHandlerFactory"));
        if (!this.context.containsKey("bpmEngineContextFactory"))
            this.context.put("bpmEngineContextFactory", engineConfiguration.getPlatformAttribute("bpmEngineContextFactory"));
        if (!this.context.containsKey("transactionContextFactory"))
            this.context.put("transactionContextFactory", engineConfiguration.getPlatformAttribute("transactionContextFactory"));
        if (!this.context.containsKey("idGeneratorFactory"))
            this.context.put("idGeneratorFactory", engineConfiguration.getPlatformAttribute("idGeneratorFactory"));*/
    }

    /**
     * 加载配置项
     */
    public void load(String group) {
        try {
            if (group == null) {
                sysConfig.clear();
                List<FieldDo[]> data = ps.getBizDao().selectList("SELECT paramgroupid,paramcode,paramvalue FROM sys_config_info", null);
                for (FieldDo[] rec : data) {
                    String groupId = (String) rec[0].getValue();
                    if (!Strings.isBlank(groupId)) {
                        Map<String, String> options = sysConfig.get(groupId);
                        if (options == null)
                            sysConfig.put(groupId, options = new HashMap<>());
                        options.put((String) rec[1].getValue(), (String) rec[2].getValue());
                    } else {
                        Map<String, String> options = sysConfig.get("");
                        if (options == null)
                            sysConfig.put("", options = new HashMap<>());
                        options.put((String) rec[1].getValue(), (String) rec[2].getValue());
                    }
                }
            } else {
                sysConfig.remove(group);
                List<FieldDo> params = new ArrayList<>();
                params.add(new FieldDo(FieldType.VARCHAR, group));
                List<FieldDo[]> data = ps.getBizDao().selectList("SELECT paramcode,paramvalue FROM sys_config_info WHERE paramgroupid=?", params);
                Map<String, String> options = new HashMap<>();
                for (FieldDo[] rec : data)
                    options.put((String) rec[0].getValue(), (String) rec[1].getValue());
                sysConfig.put(group, options);
            }
        } catch (Exception e) {
            if (log.isWarnEnabled())
                log.warn("load sys_config_info", e);
        }
    }

    public void save(CommandContext cc, CustomVo cv) {
        BizDao dao = ps.getBizDao();
        try {
            cc.beginTx();
            cv.getEntries().forEach(entry -> {
                if (entry.getFlag() == 'D') {
                    dao.update(cc.getUser(), "DELETE FROM sys_config_info WHERE paramcode=? AND paramgroupid=?", false, entry.getClassName(), entry.getName());
                } else if (entry.getFlag() == 'C') {
                    dao.update(cc.getUser(), "INSERT INTO sys_config_info(id,paramcode,paramvalue,paramgroupid) VALUES (?,?,?,?)", false, ps.getIdGenerator().getNextId("sys_config_info"), entry.getClassName(), entry.getDescription(), entry.getName());
                } else {
                    dao.update(cc.getUser(), "UPDATE sys_config_info SET paramvalue=? WHERE paramcode=? AND paramgroupid=?", false, entry.getDescription(), entry.getClassName(), entry.getName());
                }
            });
            cc.commitTx();
            cv.getEntries().forEach(entry -> {
                if (entry.getFlag() == 'D')
                    sysConfig.get(entry.getName()).remove(entry.getClassName());
                else
                    sysConfig.get(entry.getName()).put(entry.getClassName(), entry.getDescription());
            });
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error("save", e);
            cc.rollbackTx();
            if (e instanceof EasyPlatformWithLabelKeyException)
                throw new RuntimeException(I18N.getLabel(e.getMessage(), ((DaoException) e).getArgs()));
            else
                throw Lang.wrapThrow(e);
        } finally {
            cc.closeTx();
        }
    }

    /**
     * 获取配置项
     *
     * @param group
     * @param name
     * @return
     */
    public String getOption(String group, String name) {
        Map<String, String> options = sysConfig.get(group);
        if (options != null)
            return options.get(name);
        else if (config == null)
            options = engineConfiguration.getAttributes(group);
        else {
            options = config.get(group);
            if (options == null)
                options = engineConfiguration.getAttributes(group);
        }
        return options == null ? null : options.get(name);
    }

    /**
     * 获取对应组的所有配置项
     *
     * @param group
     * @return
     */
    public Map<String, String> getOptions(String group) {
        Map<String, String> options = sysConfig.get(group);
        if (options == null) {
            if (config == null)
                options = engineConfiguration.getAttributes(group);
            else {
                options = config.get(group);
                if (options == null)
                    options = engineConfiguration.getAttributes(group);
            }
        }
        if (options != null)
            return Collections.unmodifiableMap(options);
        return null;
    }

    /**
     * 返回配置项
     *
     * @return
     */
    public Map<String, Map<String, String>> getOptions() {
        return Collections.unmodifiableMap(sysConfig);
    }

    public int getAsInt(String name) {
        return getAsInt(name, 0);
    }

    public int getAsInt(String name, int def) {
        return Nums.toInt(getString(name), def);
    }

    public String getString(String name) {
        String value = null;
        if (context != null)
            value = context.get(name);
        if (Strings.isBlank(value))
            value = engineConfiguration.getPlatformAttribute(name);
        return value;
    }

    public boolean getAsBoolean(String name) {
        String value = null;
        if (context != null)
            value = context.get(name);
        if (Strings.isBlank(value))
            value = engineConfiguration.getPlatformAttribute(name);
        return value == null ? false : value.equalsIgnoreCase("true");
    }

    /**
     * 获取日期格式化
     *
     * @return
     */
    public String getDateFormat() {
        String fmt = getString("dateFormat");
        if (Strings.isBlank(fmt))
            fmt = "yyyyMMdd";
        return fmt;
    }

    /**
     * 用户登陆验证查询
     *
     * @return
     */
    public String getAuthenticationQuery() {
        String query = getString("dao.authenticationQuery");
        if (Strings.isBlank(query))
            throw new ServiceException(
                    I18N.getLabel("easyplatform.config.authenticationQuery"));
        return query;
    }

    /**
     * 用户登陆机构查询
     *
     * @return
     */
    public String getUnitQuery() {
        String query = getString("dao.userOrgQuery");
        if (Strings.isBlank(query))
            throw new ServiceException(
                    I18N.getLabel("easyplatform.config.userOrgQuery"));
        return query;
    }

    /**
     * 是否使用严格的验证模式
     *
     * @return
     */
    public int getStrictMode() {
        return getAsInt("user.strictMode");
    }

    /**
     * 项目日志级别，0，1，2，3
     *
     * @return
     */
    public int getLogLevel() {
        return getAsInt("app.logLevel", 0);
    }

    /**
     * 代理用户查询
     *
     * @return
     */
    public String getUserAgentQuery() {
        return getString("dao.userAgentQuery");
    }

    /**
     * 代理用户角色查询
     *
     * @return
     */
    public String getUserAgentRoleQuery() {
        return getString("dao.userAgentRoleQuery");
    }

    /**
     * 用户子机构查询
     *
     * @return
     */
    public String getOrgSubQuery() {
        return getString("dao.orgSubQuery");
    }

    /**
     * 币别档小数位查询
     *
     * @return
     */
    public String getAccQuery() {
        return getString("dao.accQuery");
    }

    /**
     * 最大允许登陆失败次数
     *
     * @return
     */
    public int getMaxLoginFailedTimes() {
        return getAsInt("user.maxLoginFailedTimes", UserDo.DEFAULT_MAX_LOGIN_FAILED_TIMES);
    }

    /**
     * 用户操作超时间
     *
     * @return
     */
    public int getTimeToLive() {
        return getAsInt("user.timeToLive", UserDo.DEFAULT_TIMEOUT);
    }

    /**
     * 用户操作超时间
     *
     * @return
     */
    public int getSessionTimeout() {
        return getAsInt("user.timeout", engineConfiguration.getSessionTimeout());
    }

    /**
     * 用户密码有效期提前几天提示
     *
     * @return
     */
    public int getDaysRemaining() {
        return getAsInt("user.caculateDaysRemaining");
    }

    /**
     * 设置用户密码检查正则表达式
     *
     * @return
     */
    public Pattern getCheckPasswordPattern() {
        return pattern;
    }

    /**
     * 如果密码检查正则表达式成立，给用户提示信息
     *
     * @return
     */
    public String getCheckPasswordMessage() {
        String label = getString("user.password.check.msg");
        if (Strings.isBlank(label))
            return I18N.getLabel("app.user.check.password");
        return label;
    }
}
